﻿Imports System.Linq
Imports System.Collections.Generic
Imports EnvDTE

Public Module CopyProvider

  Public Class CopyProviderSettings
    Public Property RemoveNonSourceLinks As Boolean
    Public Property ReportIgnored As Boolean = True
  End Class

  Private Class SourceTargetProjectNames
    Public Property SourceProjectName As String
    Public Property TargetProjectName As String
  End Class

  Private Const PROPERTY_ISLINK_TRUE_VALUE As String = "True"
  Private _AcceptedTypes As String() = Nothing
  Private ReadOnly Property AcceptedTypes As String()
    Get
      If _AcceptedTypes Is Nothing Then
        _AcceptedTypes = My.Settings.AcceptedTypes.Split(CChar(";"))
      End If

      Return _AcceptedTypes
    End Get
  End Property

  Public Function DoLinking( _
                           sourceProject As ProjectHolder, targetProjects As ProjectHolderCollection, _
                           sett As CopyProviderSettings) _
                         As List(Of ResultItem)

    Dim ret As New List(Of ResultItem)

    For Each ph As ProjectHolder In targetProjects
      Dim pom = DoLinking(sourceProject, ph, sett)
      ret.AddRange(pom)
    Next

    Return ret

  End Function

  Public Function DoLinking(sourceProject As ProjectHolder, targetProject As ProjectHolder, _
                            sett As CopyProviderSettings) As List(Of ResultItem)

    Dim pd As New ProcessData()
    pd.ProgressForm.Show()

    Dim names As New SourceTargetProjectNames With _
      {.SourceProjectName = sourceProject.FullName, .TargetProjectName = targetProject.FullName}

    DoLinking(names, sourceProject.Project, targetProject.Project, sett, pd)

    If sett.RemoveNonSourceLinks Then
      pd.ProgressForm.SetProjectItem("Removing links...")

      RemoveLinks(names, sourceProject.Project, targetProject.Project, pd)
    End If


    pd.ProgressForm.Close()

    Return pd.ResultItems

  End Function

  Private Sub DoLinking(names As SourceTargetProjectNames, _
                        source As Project, target As Project, sett As CopyProviderSettings, pd As ProcessData)

    pd.ProgressForm.SetTargetProject(target.Name)

    DoLinking(names, source.ProjectItems, target.ProjectItems, sett, pd)

  End Sub

  Private Function GetProjectPath(source As Project) As String
    Return System.IO.Path.GetDirectoryName(source.FullName)
  End Function

  Private Function IsFolder(pi As ProjectItem) As Boolean

    Dim fullPath As String = pi.TryGetPropertyValue(ePropertyName.FullPath)

    If System.IO.Directory.Exists(fullPath) Then
      Return True
    Else
      Return False
    End If

  End Function

  Private Function IsAcceptedFile(pi As ProjectItem) As Boolean
    Dim subtype As String = pi.TryGetPropertyValue(ePropertyName.Subtype)
    If subtype Is Nothing Then
      Return False
    End If
    If AcceptedTypes.Contains(subtype) Then
      Return True
    Else
      Return False
    End If

  End Function

  Private Function TryCreateProjectFolder(names As SourceTargetProjectNames, _
                                          parentProjectItems As ProjectItems, pi As ProjectItem, pd As ProcessData) As ProjectItem

    Dim ret As ProjectItem

    Try
      ret = parentProjectItems.Item(pi.Name)

      AddReportItem(names, pi, pd, "Folder", "Exists")
    Catch ex As Exception
      ret = Nothing
    End Try


    If ret Is Nothing Then
      Try
        ret = parentProjectItems.AddFolder(pi.Name)

        AddReportItem(names, pi, pd, "Folder", "Created")

      Catch ex As Exception

        AddReportItem(names, pi, pd, "Folder", "Error: " & ex.Message)

        ret = Nothing
      End Try
    End If

    Return ret

  End Function

  Private Sub DoLinking(names As SourceTargetProjectNames, _
                       sourceProjectItems As ProjectItems, targetProjectItems As ProjectItems, _
                       sett As CopyProviderSettings, pd As ProcessData)

    For Each pi As ProjectItem In sourceProjectItems

      pd.ProgressForm.SetProjectItem(pi.Name)

      If IsFolder(pi) Then
        Dim newFolderItem As ProjectItem = TryCreateProjectFolder(names, targetProjectItems, pi, pd)
        DoLinking(names, pi.ProjectItems, newFolderItem.ProjectItems, sett, pd)
      ElseIf IsAcceptedFile(pi) Then

        Dim newProjectItem As ProjectItem = TryLinkFile(names, targetProjectItems, pi, pd)

      Else
        If sett.ReportIgnored Then
          AddReportItem(names, pi, pd, "File", "Ignored")
        End If

      End If

    Next

  End Sub

  Private Function TryLinkFile(names As SourceTargetProjectNames, _
                               targetProjectItems As ProjectItems, fileProjectItem As ProjectItem, pd As ProcessData) As ProjectItem

    Dim ret As ProjectItem = Nothing
    Dim filePath = fileProjectItem.TryGetPropertyValue(ePropertyName.FullPath)

    Try
      ret = targetProjectItems.Item(fileProjectItem.Name)

      AddReportItem(names, fileProjectItem, pd, "File", "Exists")
    Catch ex As Exception
      ret = Nothing
    End Try

    If ret Is Nothing Then
      Try
        ret = targetProjectItems.AddFromFile(filePath)

        AddReportItem(names, fileProjectItem, pd, "File", "Linked")
      Catch ex As Exception

        ret = Nothing

        AddReportItem(names, fileProjectItem, pd, "File", "Error: " & ex.Message)

      End Try
    End If

    Return ret

  End Function

  Private Function CreateReportItem2(names As SourceTargetProjectNames, _
                              projectItem As ProjectItem, _
                              projectItemType As String, _
                              resultOfOperation As String) As ResultItem

    Dim fn = projectItem.TryGetPropertyValue(ePropertyName.FullPath)
    Dim st = projectItem.TryGetPropertyValue(ePropertyName.Subtype)

    Dim ri = New ResultItem()

    ri.SourceProjectName = names.SourceProjectName
    ri.FullPath = fn
    ri.TargetProjectName = names.TargetProjectName
    ri.Name = projectItem.Name
    ri.DTESubType = st
    ri.Type = projectItemType
    ri.Result = resultOfOperation

    Return ri
  End Function

  Private Sub AddReportItem(names As SourceTargetProjectNames, _
                              projectItem As ProjectItem, _
                              processDataToWriteInto As ProcessData, projectItemType As String, _
                              resultOfOperation As String)

    Dim ri As ResultItem = CreateReportItem2(names, projectItem, projectItemType, resultOfOperation)
    processDataToWriteInto.AddResult(ri)
  End Sub
  Private Sub AddReportItem(resultItem As ResultItem, processDataToWriteInto As ProcessData)
    processDataToWriteInto.AddResult(resultItem)
  End Sub

  Private Delegate Function IncludeFilterDelegate(item As ProjectItem) As Boolean
  Private Function ExtractCurrentItemsInProject(project As Project, includeFilter As IncludeFilterDelegate) As ProjectItemCollection
    Dim ret As New ProjectItemCollection
    Dim xl = New ProjectItemCollection

    For Each it As ProjectItem In project.ProjectItems
      xl.Add(it)
    Next

    While xl.Count > 0

      Dim current = xl.Item(0)

      If includeFilter(current) Then _
        ret.Add(current)

      If current.ProjectItems.Count > 0 Then
        For Each it As ProjectItem In current.ProjectItems
          xl.Add(it)
        Next
      End If

      xl.RemoveAt(0)
    End While

    Return ret
  End Function

  Private Function IsLink(item As ProjectItem) As Boolean

    Dim val = item.TryGetPropertyValue(ePropertyName.IsLink)

    If val = PROPERTY_ISLINK_TRUE_VALUE Then
      Return True
    Else
      Return False
    End If

  End Function

  Private Sub RemoveLinks(names As SourceTargetProjectNames, sourceProject As Project, targetProject As Project, _
                          pd As ProcessData)
    Dim linksInTarget = ExtractCurrentItemsInProject(targetProject, _
                                                     Function(x As ProjectItem) IsLink(x))
    Dim filesInSource = ExtractCurrentItemsInProject(sourceProject, _
                                                     Function(x As ProjectItem) True)
    Dim removeList As New ProjectItemCollection

    For Each item In linksInTarget
      Dim aName = item.TryGetPropertyValue(ePropertyName.FullPath).ToLower()
      If filesInSource.ContainsFullPath(aName) = False Then
        removeList.Add(item)
      End If
    Next

    If removeList.Count = 0 Then Return

    removeList = RequestUserToConfirmRemoving(removeList, names.TargetProjectName)

    If removeList Is Nothing Then Return

    For Each item In removeList

      Dim preparedRI = CreateReportItem2(names, item, "File", "Removed")

      Try
        item.Delete()
        AddReportItem(preparedRI, pd)
      Catch ex As Exception
        AddReportItem(names, item, pd, "File", "Error: " & ex.Message)
      End Try
    Next
  End Sub

  Private Function RequestUserToConfirmRemoving(removeList As ProjectItemCollection, projectName As String) As ProjectItemCollection
    Dim f As New FrmSelectProjectItems

    f.Init(removeList, "Select links to remove from project " & projectName, True)

    f.ShowDialog()
    If f.DialogResult = DialogResult.OK Then
      Return f.SelectedProjectItems
    Else
      Return Nothing
    End If
  End Function


End Module
